package pod

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"publish/shell"
	"regexp"
	"strconv"
	"strings"
)

type UpdateType string

const (
	Update_Default  UpdateType = "default"
	Update_Refactor UpdateType = "refactor"
	Update_Feature  UpdateType = "feature"
	Update_Bugfix   UpdateType = "bugfix"
)

type SpecInfo struct {
	Name      string // name of the file
	Version   string
	IsDevelop bool
	Size      int64 // length in bytes for regular files; system-dependent for others
	IsJson    bool  // abbreviation for Mode().IsDir()
	Content   string
}

func init() {

}

func CurrentVersion() (string, error) {

	return "", nil
}

func isDevelop(version string) bool {
	var validVersion = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)$`)
	return validVersion.MatchString(version)
}

// 按照规则升级版本
func UpdateVersionByRule(mode UpdateType, develop bool, repo *Repo) (err error) {

	// 清理

	// 同步

	// pull

	// update

	path, err := searchSpec()
	if err != nil {
		return err
	}

	spec, err := updateSpecByVersionOrRule(path, "", mode, develop)
	if err != nil {
		//TODO: 回滚操作
		return err
	}
	if err := repo.AddSpec(spec); err != nil {
		//TODO: 回滚
		return err
	}

	// commit
	if err := repo.Update(spec); err != nil {
		//TODO: 回滚
		return err
	}

	// push
	if err := repo.PushChanges(); err != nil {
		return err
	}

	// commit spec
	if err := spec.Update(); err != nil {
		//TODO: 回滚
		return err
	}

	// push spec
	if err := spec.PushChanges(); err != nil {
		return err
	}

	return nil
}

// 升级到指定版本
func UpdateVersion(version string, develop bool, repo *Repo) (err error) {
	if err := checkVersion(version); err != nil {
		return err
	}
	path, err := searchSpec()
	if err != nil {
		return err
	}
	spec, err := updateSpecByVersionOrRule(path, version, Update_Default, develop)
	if err := repo.AddSpec(spec); err != nil {
		//TODO: 回滚
		return err
	}
	return err
}

// xxx.podspec 文件搜索
func searchSpec() (string, error) {
	dir, err := os.Getwd()
	if err != nil {
		return "", err
	}
	match, err := filepath.Glob(dir + "/*.podspec")
	if err != nil {
		return "", err
	}
	if len(match) == 0 {
		return "", errors.New("unfound available podspec file at " + dir)
	}
	if len(match) > 1 {
		//FIXME: message check
		return "", errors.New("unsupport multe spec")
	}
	//TODO: check file name == spec.name
	return match[0], nil
}

func ReadSpecInfo(path string) (*SpecInfo, error) {
	// 读取spec内容
	fileInfo, err := os.Lstat(path)
	if os.IsNotExist(err) {
		return nil, err
	}
	if fileInfo.IsDir() {
		return nil, fmt.Errorf("%s is a folder", path)
	}
	//TODO: info里有size信息，过大时按行读取
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	content := string(buf)

	// 查找version\
	regEX := `\.version[ ]*=[ ]*['|"][0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?['|"]`
	versionLineReg, err := regexp.Compile(regEX)
	if err != nil {
		return nil, err
	}

	versionLine := versionLineReg.FindString(content)
	if len(versionLine) == 0 {
		return nil, errors.New("unfind valid version")
	}
	versionReg, err := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?`)
	if err != nil {
		return nil, err
	}
	version := versionReg.FindString(versionLine)
	if len(version) == 0 {
		return nil, errors.New("unfind valid version")
	}
	return &SpecInfo{
		Name:      "file anme",
		Version:   version,
		Size:      fileInfo.Size(),
		Content:   content,
		IsDevelop: isDevelop(version),
		IsJson:    false,
	}, nil
}

func updateSpecByVersionOrRule(path string, newVersion string, mode UpdateType, develop bool) (*SpecInfo, error) {

	// 读取spec内容
	fileInfo, err := os.Lstat(path)
	if os.IsNotExist(err) {
		return nil, err
	}
	if fileInfo.IsDir() {
		return nil, fmt.Errorf("%s is a folder", path)
	}
	//TODO: info里有size信息，过大时按行读取
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	content := string(buf)

	// 查找version\
	regEX := `\.version[ ]*=[ ]*['|"][0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?['|"]`
	versionLineReg, err := regexp.Compile(regEX)
	if err != nil {
		return nil, err
	}

	versionLine := versionLineReg.FindString(content)
	if len(versionLine) == 0 {
		return nil, errors.New("unfind valid version")
	}
	versionReg, err := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?`)
	if err != nil {
		return nil, err
	}
	version := versionReg.FindString(versionLine)
	if len(version) == 0 {
		return nil, errors.New("unfind valid version")
	}

	// 查找name
	nameRegEX := `\.name[ ]*=[ ]*['|"].+['|"]`
	nameLineReg, err := regexp.Compile(nameRegEX)
	if err != nil {
		return nil, err
	}

	nameLine := nameLineReg.FindString(content)
	if len(nameLine) == 0 {
		return nil, errors.New("unfind valid name")
	}
	nameReg, err := regexp.Compile(`['|"].+['|"]`)
	if err != nil {
		return nil, err
	}
	name := nameReg.FindString(nameLine)
	if len(name) == 0 {
		return nil, errors.New("unfind valid name")
	}
	name = strings.ReplaceAll(strings.ReplaceAll(name, "'", ""), "\"", "")

	// 计算目标version
	if len(newVersion) == 0 {
		newVersion, err = updateVersionByRule(mode, develop, version)
		if err != nil {
			return nil, err
		}
	}

	// verify tag
	_, result := shell.Exec(fmt.Sprintf("git tag -v %s", newVersion))
	if !(strings.HasPrefix(result, "error: tag") && strings.Contains(result, "not found.")) {
		return nil, fmt.Errorf("%s not available：%s", newVersion, result)
	}

	// 更新version并保持
	newVersionLine := strings.Replace(versionLine, version, newVersion, -1)
	content = strings.Replace(content, versionLine, newVersionLine, -1)
	err = ioutil.WriteFile(path, []byte(content), os.ModePerm)
	if err != nil {
		return nil, err
	}

	return &SpecInfo{
		Name:      name,
		Version:   newVersion,
		Size:      fileInfo.Size(),
		Content:   content,
		IsDevelop: isDevelop(version),
		IsJson:    false,
	}, nil
}

func fileExist(path string) bool {
	s, err := os.Lstat(path)
	return !os.IsNotExist(err) && !s.IsDir()
}

// 检测版本是否合法，x.y.z-beta.n
func checkVersion(version string) (err error) {
	//版本规则合法
	var validVersion = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$`)
	if validVersion.MatchString(version) {
		return nil
	}
	//版本不小于历史版本
	return fmt.Errorf("`%s` 不符合语义化版本规则，请使用\"x.y.z\" 或 \"x.y.x-beta.n\"，更多信息请参考：https://semver.org/lang/zh-CN/", version)
}

func updateVersionByRule(mode UpdateType, develop bool, version string) (string, error) {
	if err := checkVersion(version); err != nil {
		return "", err
	}

	if !develop && mode == Update_Default {
		if strings.Contains(version, "-beta.") {
			return strings.Split(version, "-beta.")[0], nil
		}
		return version, nil
	}

	if develop && mode == Update_Default {
		// 之前是beta版本，则升级beta.N即可
		if strings.Contains(version, "-beta.") {
			vlist := strings.Split(version, "-beta.")
			if len(vlist) != 2 {
				return "", errors.New("original version illegal，unable update beta version")
			}
			betaVersion, err := strconv.Atoi(vlist[1])
			if err != nil {
				return "", err
			}
			vlist[1] = strconv.Itoa(betaVersion + 1)
			return strings.Join(vlist, "-beta."), nil
		}
		return "", errors.New("original version illegal，unable update beta version")
	}

	vList := strings.Split(strings.Replace(version, "-beta", "", -1), ".")
	updateIndex := -1
	switch mode {
	case Update_Bugfix:
		updateIndex = 2
	case Update_Feature:
		updateIndex = 1
	case Update_Refactor:
		updateIndex = 0
	case Update_Default:
		updateIndex = -1
	}
	if updateIndex > -1 {
		updateVersion, err := strconv.Atoi(vList[updateIndex])
		if err != nil {
			return "", err
		}
		vList[updateIndex] = strconv.Itoa(updateVersion + 1)
	}
	vList = vList[0:3]
	for i := updateIndex + 1; i < len(vList); i++ {
		vList[i] = "0"
	}
	if develop {
		vList = append(vList, "1")
		vList[2] = vList[2] + "-beta"
	}
	return strings.Join(vList, "."), nil
}

func PreCheck(develop bool) error {
	/**
	开启严格模式后：正式版本发布只能在指定分支操作
	分支是否符合要求
	版本规则是否符合要求
	*/
	if develop {
		return nil
	}
	success, result := shell.Exec("git branch")
	if !success {
		return errors.New(result)
	}
	if strings.Contains(result, "* master") {
		return nil
	}
	return errors.New("publish release version should on master branch, switch to master by `git checkout master`")
}

func (spec *SpecInfo) Update() error {
	success, result := shell.Exec("git add .")
	if !success {
		return errors.New("git add . err : " + result)
	}
	success, result = shell.ExecWithOption(fmt.Sprintf("git commit -m '[Auto] update version (%s)'", spec.Version))
	if !success {
		return errors.New("git commit err : " + result)
	}
	success, result = shell.Exec("git tag " + spec.Version)
	if !success {
		return errors.New(result)
	}
	return nil
}

func (spec *SpecInfo) PushChanges() error {
	success, result := shell.Exec("git push")
	if !success {
		return errors.New(result)
	}
	success, result = shell.Exec("git push --tags")
	if !success {
		return errors.New(result)
	}
	return nil
}
